page.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. "use client";
  2. import { fetchApi, fetchFile } from "@/app/_modules/func";
  3. import {
  4. DeleteOutlined,
  5. ExclamationCircleFilled,
  6. EyeOutlined,
  7. ReloadOutlined,
  8. } from "@ant-design/icons";
  9. import type {
  10. ActionType,
  11. ProColumns,
  12. ProFormInstance,
  13. } from "@ant-design/pro-components";
  14. import {
  15. PageContainer,
  16. ProDescriptions,
  17. ProTable,
  18. } from "@ant-design/pro-components";
  19. import { Button, message, Modal, Space, Tag } from "antd";
  20. import { useRouter } from "next/navigation";
  21. import {
  22. faCheck,
  23. faDownload,
  24. faToggleOff,
  25. faToggleOn,
  26. faXmark,
  27. } from "@fortawesome/free-solid-svg-icons";
  28. import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
  29. import { useRef, useState } from "react";
  30. //查询Job详情
  31. const queryJobAPI = "/api/monitor/job";
  32. //查询表格数据API
  33. const queryAPI = "/api/monitor/jobLog/list";
  34. //删除API
  35. const deleteAPI = "/api/monitor/jobLog";
  36. //导出API
  37. const exportAPI = "/api/monitor/jobLog/export";
  38. //导出文件前缀名
  39. const exportFilePrefix = "joblog";
  40. //清空调度日志API
  41. const clearAllAPI = "/api/monitor/jobLog/clean";
  42. export default function JobLog({ params }: { params: { jobid: string } }) {
  43. const { push } = useRouter();
  44. //获取对应的任务的JobName的值
  45. const getJobName = async () => {
  46. const resp = await fetchApi(`${queryJobAPI}/${params.jobid}`, push);
  47. if (resp != undefined) {
  48. if (searchTableFormRef.current) {
  49. searchTableFormRef.current.setFieldsValue({
  50. jobName: resp.data.jobName,
  51. jobGroup: resp.data.jobGroup,
  52. });
  53. }
  54. return [resp.data.jobName, resp.data.jobGroup];
  55. }
  56. return "";
  57. };
  58. //表格列定义
  59. const columns: ProColumns[] = [
  60. {
  61. title: "日志编号",
  62. dataIndex: "jobLogId",
  63. search: false,
  64. },
  65. {
  66. title: "任务名称",
  67. fieldProps: {
  68. placeholder: "请输入任务名称",
  69. },
  70. dataIndex: "jobName",
  71. ellipsis: true,
  72. order: 4,
  73. },
  74. {
  75. title: "任务组名",
  76. dataIndex: "jobGroup",
  77. valueType: "select",
  78. valueEnum: {
  79. DEFAULT: {
  80. text: "默认",
  81. status: "DEFAULT",
  82. },
  83. SYSTEM: {
  84. text: "系统",
  85. status: "SYSTEM",
  86. },
  87. },
  88. order: 3,
  89. },
  90. {
  91. title: "调用目标字符串",
  92. dataIndex: "invokeTarget",
  93. ellipsis: true,
  94. search: false,
  95. },
  96. {
  97. title: "日志信息",
  98. dataIndex: "jobMessage",
  99. ellipsis: true,
  100. search: false,
  101. },
  102. {
  103. title: "执行状态",
  104. dataIndex: "status",
  105. valueType: "select",
  106. render: (_, record) => {
  107. return (
  108. <Space>
  109. <Tag
  110. color={record.status === "0" ? "green" : "red"}
  111. icon={
  112. record.status == 0 ? (
  113. <FontAwesomeIcon icon={faCheck} />
  114. ) : (
  115. <FontAwesomeIcon icon={faXmark} />
  116. )
  117. }
  118. >
  119. {_}
  120. </Tag>
  121. </Space>
  122. );
  123. },
  124. valueEnum: {
  125. 0: {
  126. text: "成功",
  127. status: "0",
  128. },
  129. 1: {
  130. text: "失败",
  131. status: "1",
  132. },
  133. },
  134. order: 2,
  135. },
  136. {
  137. title: "执行时间",
  138. dataIndex: "createTime",
  139. valueType: "dateTime",
  140. search: false,
  141. },
  142. {
  143. title: "执行时间",
  144. fieldProps: {
  145. placeholder: ["开始日期", "结束日期"],
  146. },
  147. dataIndex: "createTimeRange",
  148. valueType: "dateRange",
  149. hideInTable: true,
  150. order: 1,
  151. search: {
  152. transform: (value) => {
  153. return {
  154. "params[beginTime]": `${value[0]} 00:00:00`,
  155. "params[endTime]": `${value[1]} 23:59:59`,
  156. };
  157. },
  158. },
  159. },
  160. {
  161. title: "操作",
  162. key: "option",
  163. search: false,
  164. render: (_, record) => [
  165. <Button
  166. key="detailBtn"
  167. type="link"
  168. icon={<EyeOutlined />}
  169. onClick={() => onClickShowRowDetailModal(record)}
  170. >
  171. 详情
  172. </Button>,
  173. ],
  174. },
  175. ];
  176. //0.查询表格数据
  177. const queryTableData = async (params: any, sorter: any, filter: any) => {
  178. const searchParams = {
  179. pageNum: params.current,
  180. ...params,
  181. };
  182. delete searchParams.current;
  183. const queryParams = new URLSearchParams(searchParams);
  184. //如果没有带上默认的字典类型,查询绑定上
  185. if (!("jobName" in searchParams)) {
  186. const result = await getJobName();
  187. queryParams.append("jobName", result[0]);
  188. queryParams.append("jobGroup", result[1]);
  189. }
  190. Object.keys(sorter).forEach((key) => {
  191. queryParams.append("orderByColumn", key);
  192. if (sorter[key] === "ascend") {
  193. queryParams.append("isAsc", "ascending");
  194. } else {
  195. queryParams.append("isAsc", "descending");
  196. }
  197. });
  198. const body = await fetchApi(`${queryAPI}?${queryParams}`, push);
  199. return body;
  200. };
  201. //操作当前数据的附加数据
  202. const [operatRowData, setOperateRowData] = useState<{
  203. [key: string]: any;
  204. }>({});
  205. //3.删除
  206. //点击删除按钮,展示删除确认框
  207. const onClickDeleteRow = (record?: any) => {
  208. const jobLogId =
  209. record != undefined ? record.jobLogId : selectedRowKeys.join(",");
  210. Modal.confirm({
  211. title: "系统提示",
  212. icon: <ExclamationCircleFilled />,
  213. content: `确定删除调度日志编号为“${jobLogId}”的数据项?`,
  214. onOk() {
  215. executeDeleteRow(jobLogId);
  216. },
  217. onCancel() {},
  218. });
  219. };
  220. //确定删除选中的数据
  221. const executeDeleteRow = async (jobLogId: any) => {
  222. const body = await fetchApi(`${deleteAPI}/${jobLogId}`, push, {
  223. method: "DELETE",
  224. });
  225. if (body !== undefined) {
  226. if (body.code == 200) {
  227. message.success("删除成功");
  228. //删除按钮变回不可点击
  229. setRowCanDelete(false);
  230. //选中行数据重置为空
  231. setSelectedRowKeys([]);
  232. //刷新列表
  233. if (actionTableRef.current) {
  234. actionTableRef.current.reload();
  235. }
  236. } else {
  237. message.error(body.msg);
  238. }
  239. }
  240. };
  241. //弹出清空确认框
  242. const onClickClearAll = () => {
  243. Modal.confirm({
  244. title: "系统提示",
  245. icon: <ExclamationCircleFilled />,
  246. content: `确定清空所有调度日志数据项?`,
  247. onOk() {
  248. executeClearAll();
  249. },
  250. onCancel() {},
  251. });
  252. };
  253. //执行清空调度日志
  254. const executeClearAll = async () => {
  255. const body = await fetchApi(clearAllAPI, push, {
  256. method: "DELETE",
  257. });
  258. if (body !== undefined) {
  259. if (body.code == 200) {
  260. message.success("清空成功");
  261. if (actionTableRef.current) {
  262. actionTableRef.current.reload();
  263. }
  264. } else {
  265. message.error(body.msg);
  266. }
  267. } else {
  268. message.error("清空发生异常");
  269. }
  270. };
  271. //4.导出
  272. //导出表格数据
  273. const exportTable = async () => {
  274. if (searchTableFormRef.current) {
  275. const formData = new FormData();
  276. const data = {
  277. pageNum: page,
  278. pageSize: pageSize,
  279. ...searchTableFormRef.current.getFieldsValue(),
  280. };
  281. Object.keys(data).forEach((key) => {
  282. if (data[key] !== undefined) {
  283. formData.append(key, data[key]);
  284. }
  285. });
  286. await fetchFile(
  287. exportAPI,
  288. push,
  289. {
  290. method: "POST",
  291. body: formData,
  292. },
  293. `${exportFilePrefix}_${new Date().getTime()}.xlsx`
  294. );
  295. }
  296. };
  297. //5.选择行
  298. //选中行操作
  299. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  300. const [selectedRow, setSelectedRow] = useState(undefined as any);
  301. //删除按钮是否可用,选中行时才可用
  302. const [rowCanDelete, setRowCanDelete] = useState(false);
  303. //ProTable rowSelection
  304. const rowSelection = {
  305. onChange: (newSelectedRowKeys: React.Key[], selectedRows: any[]) => {
  306. setSelectedRowKeys(newSelectedRowKeys);
  307. setRowCanDelete(newSelectedRowKeys && newSelectedRowKeys.length > 0);
  308. if (newSelectedRowKeys && newSelectedRowKeys.length == 1) {
  309. setSelectedRow(selectedRows[0]);
  310. } else {
  311. setSelectedRow(undefined);
  312. }
  313. },
  314. //复选框的额外禁用判断
  315. // getCheckboxProps: (record) => ({
  316. // disabled: record.userId == 1,
  317. // }),
  318. };
  319. //搜索栏显示状态
  320. const [showSearch, setShowSearch] = useState(true);
  321. //action对象引用
  322. const actionTableRef = useRef<ActionType>();
  323. //搜索表单对象引用
  324. const searchTableFormRef = useRef<ProFormInstance>();
  325. //当前页数和每页条数
  326. const [page, setPage] = useState(1);
  327. const defaultPageSize = 10;
  328. const [pageSize, setPageSize] = useState(defaultPageSize);
  329. const pageChange = (page: number, pageSize: number) => {
  330. setPage(page);
  331. setPageSize(pageSize);
  332. };
  333. const [isShowDetail, setIsShowDetail] = useState(false);
  334. //展示详情框
  335. const onClickShowRowDetailModal = (record: any) => {
  336. setIsShowDetail(true);
  337. setSelectedRow(record);
  338. };
  339. return (
  340. <PageContainer
  341. header={{
  342. title: "调度日志",
  343. onBack(e) {
  344. push("/monitor/job");
  345. },
  346. }}
  347. >
  348. <ProTable
  349. formRef={searchTableFormRef}
  350. rowKey="jobLogId"
  351. rowSelection={{
  352. selectedRowKeys,
  353. ...rowSelection,
  354. }}
  355. columns={columns}
  356. request={async (params: any, sorter: any, filter: any) => {
  357. // 表单搜索项会从 params 传入,传递给后端接口。
  358. const data = await queryTableData(params, sorter, filter);
  359. if (data !== undefined) {
  360. return Promise.resolve({
  361. data: data.rows,
  362. success: true,
  363. total: data.total,
  364. });
  365. }
  366. return Promise.resolve({
  367. data: [],
  368. success: true,
  369. });
  370. }}
  371. pagination={{
  372. defaultPageSize: defaultPageSize,
  373. showQuickJumper: true,
  374. showSizeChanger: true,
  375. onChange: pageChange,
  376. }}
  377. search={
  378. showSearch
  379. ? {
  380. defaultCollapsed: false,
  381. searchText: "搜索",
  382. }
  383. : false
  384. }
  385. dateFormatter="string"
  386. actionRef={actionTableRef}
  387. toolbar={{
  388. actions: [
  389. <Button
  390. key="danger"
  391. danger
  392. icon={<DeleteOutlined />}
  393. disabled={!rowCanDelete}
  394. onClick={() => onClickDeleteRow()}
  395. >
  396. 删除
  397. </Button>,
  398. <Button
  399. key="danger"
  400. danger
  401. icon={<DeleteOutlined />}
  402. onClick={() => onClickClearAll()}
  403. >
  404. 清空
  405. </Button>,
  406. <Button
  407. key="export"
  408. type="primary"
  409. icon={<FontAwesomeIcon icon={faDownload} />}
  410. onClick={exportTable}
  411. >
  412. 导出
  413. </Button>,
  414. ],
  415. settings: [
  416. {
  417. key: "switch",
  418. icon: showSearch ? (
  419. <FontAwesomeIcon icon={faToggleOn} />
  420. ) : (
  421. <FontAwesomeIcon icon={faToggleOff} />
  422. ),
  423. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  424. onClick: (key: string | undefined) => {
  425. setShowSearch(!showSearch);
  426. },
  427. },
  428. {
  429. key: "refresh",
  430. tooltip: "刷新",
  431. icon: <ReloadOutlined />,
  432. onClick: (key: string | undefined) => {
  433. if (actionTableRef.current) {
  434. actionTableRef.current.reload();
  435. }
  436. },
  437. },
  438. ],
  439. }}
  440. />
  441. {selectedRow !== undefined && (
  442. <Modal
  443. title="调度日志详情"
  444. footer={<Button onClick={() => setIsShowDetail(false)}>关闭</Button>}
  445. open={isShowDetail}
  446. onCancel={() => setIsShowDetail(false)}
  447. >
  448. <ProDescriptions column={2}>
  449. <ProDescriptions.Item label="日志序号">
  450. {selectedRow.jobLogId}
  451. </ProDescriptions.Item>
  452. <ProDescriptions.Item
  453. label="任务分组"
  454. valueEnum={{
  455. DEFAULT: {
  456. text: "默认",
  457. status: "DEFAULT",
  458. },
  459. SYSTEM: {
  460. text: "系统",
  461. status: "SYSTEM",
  462. },
  463. }}
  464. >
  465. {selectedRow.jobGroup}
  466. </ProDescriptions.Item>
  467. <ProDescriptions.Item label="任务名称">
  468. {selectedRow.jobName}
  469. </ProDescriptions.Item>
  470. <ProDescriptions.Item label="执行时间">
  471. {selectedRow.createTime}
  472. </ProDescriptions.Item>
  473. </ProDescriptions>
  474. <ProDescriptions column={1}>
  475. <ProDescriptions.Item label="调用目标方法">
  476. {selectedRow.invokeTarget}
  477. </ProDescriptions.Item>
  478. <ProDescriptions column={1}>
  479. <ProDescriptions.Item label="日志信息">
  480. {selectedRow.jobMessage}
  481. </ProDescriptions.Item>
  482. <ProDescriptions.Item
  483. label="执行状态"
  484. valueEnum={{
  485. 0: {
  486. text: "正常",
  487. status: "0",
  488. },
  489. 1: {
  490. text: "暂停",
  491. status: "1",
  492. },
  493. }}
  494. >
  495. {selectedRow.status}
  496. </ProDescriptions.Item>
  497. </ProDescriptions>
  498. </ProDescriptions>
  499. </Modal>
  500. )}
  501. </PageContainer>
  502. );
  503. }